/*
**  Message.m
**
**  Copyright (c) 2001, 2002
**
**  Author: Ludovic Marcotte <ludovic@Sophos.ca>
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**  
**  This library is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**  Lesser General Public License for more details.
**  
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#import <Pantomime/Message.h>

#import <Pantomime/Constants.h>
#import <Pantomime/Folder.h>
#import <Pantomime/NSStringExtensions.h>
#import <Pantomime/NSDataExtensions.h>

#include <time.h>

@implementation Message

//
//
//
- (id) init
{
  self = [super init];
  
  // We initialize our recipiens array
  recipients = [[NSMutableArray alloc] init];

  // We initialize our dictionary that will hold all our headers
  // We initialize it with a capacity of 25. This is a empirical number 
  // that is used to speedup the addition of headers w/o reallocating
  // our array everytime we add a new element.
  headers = [[NSMutableDictionary alloc] initWithCapacity: 25];
  
  // We initialize our headers with some basic values in case they are missing in the messages we decode
  [headers setObject: @"text/plain" forKey: @"Content-Type"];

  // FIXME: Should we always initialize with RECENT? or just nothing?
  flags = [[Flags alloc] initWithFlags: RECENT];

  // By default, we want the subclass's rawSource method to be called so we set our
  // rawSource ivar to nil. If it's not nil (ONLY set in initWithString), it'll be returned.
  // for performances improvements.
  rawSource = nil;

  // We initialize our dictionary holding all extra properties a message might have
  extraProperties = [[NSMutableDictionary alloc] init];
  
  // Initially, we don't have any references
  [self setReferences: nil];
  

  [self setInitialized: NO];
  [self setSize: 0];

  return self;
}


//
//
//
- (id) initWithData: (NSData *) theData
{
  NSRange aRange;

  aRange = [theData rangeOfCString: "\n\n"];

  if (aRange.length == 0)
    {
      NSDebugLog(@"Message: failed to initialize message from data.");
      AUTORELEASE(self);
      return nil;
    }
  
  // We initialize our message with the headers and the content
  self = [self init];
  
  [self setHeadersFromData: [theData subdataWithRange: NSMakeRange(0,aRange.location)]];
  [self setContentFromRawSource:
  	  [theData subdataWithRange:
  		     NSMakeRange(aRange.location + 2, [theData length]-(aRange.location+2))]];
  
 
  //
  // We can tell now that this message is fully initialized
  // NOTE: We must NOT call [self setInitialize: YES] since
  //       it will call the method from the subclass and may do
  //       extremely weird things.
  initialized = YES;

  // We set our rawSource ivar for performances reason
  [self setRawSource: theData];

  return self;
}


//
//
//
- (id) initWithData: (NSData *) theData
	    charset: (NSString *) theCharset
{
  [self setDefaultCharset: theCharset];

  return [self initWithData: theData];
}


//
//
//
- (id) initWithHeadersFromData: (NSData *) theHeaders
{
  self = [self init];

  [self setHeadersFromData: theHeaders];

  return self;
}


//
//
//
- (id) initWithHeaders: (NSDictionary *) theHeaders
{
  self = [self init];
  
  [self setHeaders: theHeaders];
  
  return self;
}


//
//
//
- (void) dealloc
{
  //NSDebugLog(@"Message: -dealloc");

  RELEASE(recipients);
  RELEASE(headers);
  RELEASE(flags);

  RELEASE(extraProperties);
  
  RELEASE(references);

  TEST_RELEASE(rawSource);
  
  [super dealloc];
}


//
// NSCoding protocol
//
- (void) encodeWithCoder: (NSCoder *) theCoder
{
  [super encodeWithCoder: theCoder];

  [theCoder encodeObject: [self recipients]];
  [theCoder encodeObject: [self allHeaders]];
  [theCoder encodeObject: [NSNumber numberWithInt: [self messageNumber]]];
  [theCoder encodeObject: [self flags]];
}


//
//
//
- (id) initWithCoder: (NSCoder *) theCoder
{
  self = [super init];
  
  [super initWithCoder: theCoder];

  [self setRecipients: [theCoder decodeObject]];
  [self setHeaders: [theCoder decodeObject]];
  [self setMessageNumber: [[theCoder decodeObject] intValue]];
  [self setFlags: [theCoder decodeObject]];

  // It's very important to set this ivar to NO since we don't serialize the content
  // or our message.
  initialized = NO;

  // We initialize the rest of our ivars
  folder = nil;
  rawSource = nil;

  // We initialize our dictionary holding all extra properties a message might have
  extraProperties = [[NSMutableDictionary alloc] init];

  return self;
}


//
//
//
- (InternetAddress *) from
{
  return [headers objectForKey: @"From"];
}


//
//
//
- (void) setFrom: (InternetAddress *) theInternetAddress
{
  [headers setObject: theInternetAddress
	   forKey: @"From"];
}


//
//
//
- (int) messageNumber
{
  return messageNumber;
}


//
//
//
- (void) setMessageNumber: (int) theMessageNumber
{
  messageNumber = theMessageNumber;
}


//
//
//
- (NSString *) messageID
{
  return [headers objectForKey: @"Message-ID"];
}


//
//
//
- (void) setMessageID: (NSString *) theMessageID
{
  [headers setObject: theMessageID
	   forKey: @"Message-ID"];
}


//
//
//
- (NSCalendarDate *) receivedDate
{
  return [headers objectForKey: @"Date"];
}


//
//
//
- (void) setReceivedDate: (NSCalendarDate*) theDate
{
  [headers setObject: theDate 
	   forKey: @"Date"];
}


//
//
//
- (void) addToRecipients: (InternetAddress *) theAddress
{
  if ( theAddress )
    {
      [recipients addObject: theAddress];
    }
}


//
//
//
- (void) removeFromRecipients: (InternetAddress *) theAddress
{
  if ( theAddress )
    {
      [recipients removeObject: theAddress];
    }
}


//
//
//
- (NSArray *) recipients
{
  return recipients;
}


//
//
//
- (void) setRecipients: (NSArray *) theRecipients
{
  if ( theRecipients )
    {
      NSMutableArray *newRecipients;
      
      newRecipients = [NSMutableArray arrayWithArray: theRecipients];
      RELEASE(recipients);
      RETAIN(newRecipients);
      recipients = newRecipients;
    }
  else
    {
      RELEASE(recipients);
      recipients = nil;
    }
}


//
//
//
- (int) recipientsCount
{
  return [[self recipients] count];
}


//
//
//
- (void) removeAllRecipients
{
  [recipients removeAllObjects];
}


//
//
//
- (InternetAddress *) replyTo
{
  return [headers objectForKey: @"Reply-To"];
}


//
//
//
- (void) setReplyTo: (InternetAddress *) theInternetAddress
{
  if ( theInternetAddress )
    {
      [headers setObject: theInternetAddress
	       forKey: @"Reply-To"];
    }
  else
    {
      [headers removeObjectForKey: @"Reply-To"];
    }
}


//
//
//
- (NSString *) subject
{
  return [headers objectForKey: @"Subject"];
}


//
//
//
- (void) setSubject: (NSString *) theSubject
{
  [headers setObject: theSubject
  	   forKey: @"Subject"];
}


//
//
//
- (BOOL) isInitialized
{
  return initialized;
}


//
//
//
- (void) setInitialized: (BOOL) aBOOL
{
  initialized = aBOOL;
}


//
// Implementation of the Part protocol follows...
//


- (NSString *) contentDisposition
{
  return [headers objectForKey: @"Content-Disposition"];
}

- (void) setContentDisposition: (NSString *) theContentDisposition
{
  [super setContentDisposition: theContentDisposition];
  
  [headers setObject: theContentDisposition
	   forKey: @"Content-Disposition"];
}

//
//
//
- (NSString *) contentID
{
  return [headers objectForKey: @"Content-Id"];
}        


//
//
//
- (void) setContentID: (NSString *) theContentID
{ 
  [super setContentID: theContentID];
  [headers setObject: theContentID
	   forKey: @"Content-Id"];
}  


//
//
//
- (NSString *) contentType
{
  return [headers objectForKey: @"Content-Type"];
}


//
//
//
- (void) setContentType: (NSString*) theContentType
{
  [super setContentType: theContentType];
  
  [headers setObject: theContentType
	   forKey: @"Content-Type"];
}

//
// See RFC1521 (MIME) for more details.
// The Content-Type MUST be defined BEFORE calling this function otherwise
// the content will be considered as a pure string.
//
- (void) setContentFromRawSource: (NSData *) theData
{
  [MimeUtility setContentFromRawSource: theData
	       inPart: self];
}

//
//
//
- (NSString *) organization
{
  return [headers objectForKey: @"Organization"];
}


//
//
//
- (void) setOrganization: (NSString *) theOrganization
{
  [headers setObject: theOrganization
	   forKey: @"Organization"];
}


//
//
//
- (id) extraPropertyForKey: (id) theKey
{
  return [extraProperties objectForKey: theKey];
}


//
//
//
- (void) setExtraProperty: (id) theProperty
                   forKey: (id) theKey
{
  [extraProperties setObject: theProperty
		   forKey: theKey];
}


//
//
//
- (NSArray *) allReferences
{
  return references;
}


//
//
//
- (void) setReferences: (NSArray *) theReferences
{
  if ( theReferences )
    {
      RETAIN(theReferences);
      RELEASE(references);
      references = theReferences;
    }
  else
    {
      DESTROY(references);
    }
}


//
//
//
- (Flags *) flags
{
  return flags;
}


//
//
//
- (void) setFlags: (Flags*) theFlags
{
  RELEASE(flags);
  flags = RETAIN(theFlags);
}


//
//
//
- (NSString *) mimeVersion
{
  return [headers objectForKey: @"MIME-Version"];
}


//
//
//
- (void) setMimeVersion: (NSString *) theMimeVersion
{
  [headers setObject: theMimeVersion
	   forKey: @"MIME-Version"];
}


//
// The message will NOT have a content other than the PLAIN/TEXT one(s).
//
- (Message *) replyWithReplyToAll: (BOOL) flag
{
  InternetAddress *anInternetAddress;
  Message *theMessage;

  NSMutableString *aMutableString;
  id aContent;
  int i;

  BOOL needsToQuote = NO;
  
  theMessage = [[Message alloc] init];

  // We set the subject of our message 
  if ( [[[self subject] lowercaseString] hasPrefix: @"re"] )
    {
      [theMessage setSubject: [self subject] ];
    }
  else 
    {
      [theMessage setSubject: [NSString stringWithFormat: @"Re: %@", [self subject]] ];
    }

  // If Reply-To is defined, we use it. Otherwise, we use From:
  if ( [self replyTo] == nil )
    {
      anInternetAddress = [self from];
    }
  else 
    {
      anInternetAddress = [self replyTo];
    }

  [anInternetAddress setType: TO];
  [theMessage addToRecipients: anInternetAddress];
  
  // We add our In-Reply-To header
  if ( [self messageID] )
    {
      [theMessage addHeader: @"In-Reply-To"
		  withValue: [self messageID]];
    }
  
  // If we reply to all, we add the other recipients
  if ( flag )
    {
      NSEnumerator *anEnumerator;

      anEnumerator = [[self recipients] objectEnumerator];
      
      while ((anInternetAddress = [anEnumerator nextObject]))
	{
	  [anInternetAddress setType: CC];
	  [theMessage addToRecipients: anInternetAddress];
	}
    }

  // We finally work on the content of the message
  aMutableString = [[NSMutableString alloc] init];
  [aMutableString appendString: @"On "];
  [aMutableString appendString: [[self receivedDate] description] ];
  [aMutableString appendString: [NSString stringWithFormat:@" %@", [[self from] unicodeStringValue]]];
  [aMutableString appendString: @" wrote:\n\n"];

  // We give a default value to aContent
  aContent = nil;
  
  //
  // We now get the right text part of the message.
  // 
  // If it's a text/enriched or a text/html, we must remove all the
  // formatting codes and keep only the 'real' text contained in that part.
  //
  if ( [self isMimeType: @"text": @"*"] )
    {
      aContent = (NSString *)[self content];
      needsToQuote = YES;
    }
  //
  // If our message only contains the following part types, we cannot
  // represent those in a reply.
  // 
  else if ( [self isMimeType: @"application": @"*"] ||
	    [self isMimeType: @"audio": @"*"] ||
	    [self isMimeType: @"image": @"*"] || 
	    [self isMimeType: @"message": @"*"] ||
	    [self isMimeType: @"video": @"*"] )
    {
      aContent = [NSString stringWithString: @"\t[NON-Text Body part not included]"];
      needsToQuote = NO;
    }
  //
  // We have a multipart type. It can be:
  //
  // multipart/appledouble, multipart/alternative, multipart/related,
  // multipart/mixed or even multipart/report.
  //
  // We must search for a text part to use in our reply.
  //
  else if ( [self isMimeType: @"multipart" : @"*"] )
    {
      MimeMultipart *aMimeMultipart;
      MimeBodyPart *aMimeBodyPart;
      
      aMimeMultipart = (MimeMultipart *)[self content];
      
      for (i = 0; i < [aMimeMultipart count]; i++)
	{
	  aMimeBodyPart = [aMimeMultipart bodyPartAtIndex: i];
	  
	  //
	  // We do a full verification on the Content-Type since we might
	  // have a text/x-{something} like text/x-vcard.
	  //
	  if ( [aMimeBodyPart isMimeType: @"text" : @"plain"] ||
	       [aMimeBodyPart isMimeType: @"text" : @"enriched"] ||
	       [aMimeBodyPart isMimeType: @"text" : @"html"] )
	    {
	      // If our part was base64 encoded, we must convert the 
	      // NSData object into a NSString
	      if ( [aMimeBodyPart contentTransferEncoding] == BASE64 &&
		   [[aMimeBodyPart content] isKindOfClass: [NSData class]] )
		{
		  aContent = [[NSString alloc] initWithData: (NSData *)[aMimeBodyPart content]
					       encoding: NSASCIIStringEncoding];
		  AUTORELEASE(aContent);
		}
	      else
		{
		  aContent = (NSString*)[aMimeBodyPart content];
		}
	      break;
	    }
	  //
	  // If we have a multipart/alternative contained in our multipart/mixed (or related)
	  // we find the text/plain part contained in this multipart/alternative object.
	  //
	  else if ( [aMimeBodyPart isMimeType: @"multipart" : @"alternative"] )
	    {
	      MimeMultipart *anOtherMimeMultipart;
	      int j;
	      
	      anOtherMimeMultipart = (MimeMultipart *)[aMimeBodyPart content];
	      
	      for (j = 0; j < [anOtherMimeMultipart count]; j++)
		{
		  aMimeBodyPart = [anOtherMimeMultipart bodyPartAtIndex: i];
		  
		  if ( [aMimeBodyPart isMimeType: @"text" : @"plain"] ||
		       [aMimeBodyPart isMimeType: @"text" : @"enriched"] ||
		       [aMimeBodyPart isMimeType: @"text" : @"html"] )
		    {
		      // If our part was base64 encoded, we must convert the 
		      // NSData object into a NSString
		      if ( [aMimeBodyPart contentTransferEncoding] == BASE64 )
			{
			  aContent = [[NSString alloc] initWithData: (NSData *)[aMimeBodyPart content]
						      encoding: NSASCIIStringEncoding];
			  AUTORELEASE(aContent);
			}
		      else
			{
			  aContent = (NSString *)[aMimeBodyPart content];
			}
		      break;
		    } 
		}
	    } // else if (...)
	}
      
      needsToQuote = YES;
    }
  
  //
  // It was impossible for use to find a text/plain part. Let's
  // inform our user that we can't do anything with this message.
  //
  if ( !aContent || [aContent isKindOfClass: [NSData class]])
    {
      aContent = [NSString stringWithString: @"\t[NON-Text Body part not included]"];
      needsToQuote = NO;
    }
  else
    {
      // We remove the signature
      NSRange aRange;
      
      aRange = [aContent rangeOfString: @"\n-- "
			options: NSBackwardsSearch];
      
      // We found it!
      if ( aRange.length )
	{
	  aContent = [aContent substringToIndex: aRange.location];
	}
    }

  // We now have our content as string, let's 'quote' it
  if ( aContent && needsToQuote )
    {
      aContent = [MimeUtility unwrapPlainTextString: aContent
			     usingQuoteWrappingLimit: 78];
      [aMutableString appendString: [MimeUtility quotePlainTextString: aContent
						 quoteLevel: 1
						 wrappingLimit: 80]];
    }
  else if (aContent && !needsToQuote)
    {
      [aMutableString appendString: aContent];
    }
  
  [theMessage setContent: aMutableString];

  RELEASE(aMutableString);
  
  return AUTORELEASE(theMessage);
}


//
// The message WILL have a content.
//
- (Message *) forward
{
  NSMutableString *aMutableString;
  Message *theMessage;
 
  theMessage = [[Message alloc] init];
  
  // We set the subject of our message
  [theMessage setSubject: [NSString stringWithFormat:@"%@ (fwd)", [self subject]] ];

  // We create our generic forward message header
  aMutableString = [[NSMutableString alloc] init];
  AUTORELEASE(aMutableString);

  [aMutableString appendString: @"---------- Forwarded message ----------\n"];
  [aMutableString appendString: @"Date: "];
  [aMutableString appendString: [[self receivedDate] description] ];
  [aMutableString appendString: @"\nFrom: "];
  [aMutableString appendString: [[self from] unicodeStringValue]];
  [aMutableString appendString: @"\nSubject: "];
  [aMutableString appendString: [NSString stringWithFormat:@"%@\n\n", [self subject]] ];
  
  //
  // If our Content-Type is text/plain, we represent it as a it is, otherwise,
  // we currently create a new body part representing the forwarded message.
  //
  if ( [self isMimeType: @"text": @"*"] )
    {
      // Our message is a text/plain one
      if ( [self isMimeType: @"text" : @"plain"] )
	{
	  // We set the content of our message
	  [aMutableString appendString: (NSString*)[self content]];
	  
	  // We set the Content-Transfer-Encoding and the Charset to the previous one
	  [theMessage setContentTransferEncoding: [self contentTransferEncoding]];
	  [theMessage setCharset: [self charset]];

	  [theMessage setContent: aMutableString];
	  [theMessage setSize: [aMutableString length]];
	}
      // Our message is either a text/enriched, text/html or something else (but text/*).
      else
	{
	  MimeMultipart *aMimeMultipart;
	  MimeBodyPart *aMimeBodyPart;

	  aMimeMultipart = [[MimeMultipart alloc] init];
	
	  // We add our text/plain part (using Part's default initialized values)
	  aMimeBodyPart = [[MimeBodyPart alloc] init];
	  [aMimeBodyPart setContent: aMutableString];
	  [aMimeBodyPart setContentDisposition: @"inline"];
	  [aMimeBodyPart setSize: [aMutableString length]];
	  [aMimeMultipart addBodyPart: aMimeBodyPart];
	  RELEASE(aMimeBodyPart);
	  
	  // We add our other text part as a attachment
	  aMimeBodyPart = [[MimeBodyPart alloc] init];
	  [aMimeBodyPart setContentType: [self contentType]];
	  [aMimeBodyPart setContent: [self content]];
	  [aMimeBodyPart setContentTransferEncoding: [self contentTransferEncoding]];
	  [aMimeBodyPart setCharset: [self charset]];
	  [aMimeBodyPart setContentDisposition: @"attachment"];
	  [aMimeBodyPart setSize: [self size]];
	  [aMimeMultipart addBodyPart: aMimeBodyPart];
	  RELEASE(aMimeBodyPart);

	  [theMessage setContent: aMimeMultipart];
	  RELEASE(aMimeMultipart);
	}
    }
  //
  // If our Content-Type is a message/rfc822 or any other type like
  // application/*, audio/*, image/* or video/*
  // 
  else if ( [self isMimeType: @"application": @"*"] ||
	    [self isMimeType: @"audio": @"*"] ||
	    [self isMimeType: @"image": @"*"] || 
	    [self isMimeType: @"message": @"*"] ||
	    [self isMimeType: @"video": @"*"] )
    {
      MimeMultipart *aMimeMultipart;
      MimeBodyPart *aMimeBodyPart;
      
      aMimeMultipart = [[MimeMultipart alloc] init];
      
      // We add our text/plain part.
      aMimeBodyPart = [[MimeBodyPart alloc] init];
      [aMimeBodyPart setContent: aMutableString];
      [aMimeBodyPart setContentDisposition: @"inline"];
      [aMimeBodyPart setSize: [aMutableString length]];
      [aMimeMultipart addBodyPart: aMimeBodyPart];
      RELEASE(aMimeBodyPart);
      
      // We add our content as an attachment
      aMimeBodyPart = [[MimeBodyPart alloc] init];  
      [aMimeBodyPart setContentType: [self contentType]];
      [aMimeBodyPart setContent: [self content]];
      [aMimeBodyPart setContentTransferEncoding: [self contentTransferEncoding]];
      [aMimeBodyPart setContentDisposition: @"attachment"];
      [aMimeBodyPart setCharset: [self charset]];
      [aMimeBodyPart setFilename: [self filename]];
      [aMimeBodyPart setSize: [self size]];
      [aMimeMultipart addBodyPart: aMimeBodyPart];
      RELEASE(aMimeBodyPart);

      [theMessage setContent: aMimeMultipart];
      RELEASE(aMimeMultipart);
    }
  //
  // We have a multipart object. We must treat multipart/alternative
  // parts differently since we don't want to include multipart parts in the forward.
  //
  else if ( [self isMimeType: @"multipart" : @"*"] )
    {
      //
      // If we have multipart/alternative part, we only keep one part from it.
      //
      if ( [self isMimeType: @"multipart" : @"alternative"] )
	{
	  MimeMultipart *aMimeMultipart;
	  MimeBodyPart *aMimeBodyPart;
	  int i;
	  
	  aMimeMultipart = (MimeMultipart *)[self content];
	  aMimeBodyPart = nil;

	  // We search for our text/plain part
	  for (i = 0; i < [aMimeMultipart count]; i++)
	    {
	      aMimeBodyPart = [aMimeMultipart bodyPartAtIndex: i];

	      if ( [aMimeBodyPart isMimeType: @"text": @"plain"] )
		{
		  break;
		}
	      else
		{
		  aMimeBodyPart = nil;
		}
	    }

	  // We found one
	  if ( aMimeBodyPart )
	    {
	      NSString *aString;
	      
	      // If our part was base64 encoded, we must convert the 
	      // NSData object into a NSString
	      if ( [aMimeBodyPart contentTransferEncoding] == BASE64 &&
		   [[aMimeBodyPart content] isKindOfClass: [NSData class]] )
		{
		  aString = [[NSString alloc] initWithData: (NSData *)[aMimeBodyPart content]
					      encoding: NSASCIIStringEncoding];
		  AUTORELEASE(aString);
		}
	      else
		{
		  aString = (NSString*)[aMimeBodyPart content];
		}

	      // We set the content of our message
	      [aMutableString appendString: aString];
	      
	      // We set the Content-Transfer-Encoding and the Charset to our text part
	      [theMessage setContentTransferEncoding: [aMimeBodyPart contentTransferEncoding]];
	      [theMessage setCharset: [aMimeBodyPart charset]];
	      
	      [theMessage setContent: aMutableString];
	      [theMessage setSize: [aMutableString length]];
	    }
	  // We haven't found one! Inform the user that it happened.
	  else
	    {
	      [aMutableString appendString: @"No text/plain part from this multipart/alternative part has been found"];
	      [aMutableString appendString: @"\nNo parts have been included as attachments with this mail during the forward operation."];
	      [aMutableString appendString: @"\n\nPlease report this as a bug."];

	      [theMessage setContent: aMutableString];
	      [theMessage setSize: [aMutableString length]];
	    }
	}
      //
      // We surely have a multipart/mixed or multipart/related.
      // We search for a text/plain part inside our multipart object.
      // We 'keep' the other parts in a separate new multipart object too
      // that will become our new content.
      //
      else
	{
	  MimeMultipart *aMimeMultipart, *newMimeMultipart;
	  MimeBodyPart *aMimeBodyPart;
	  BOOL hasFoundTextPlain = NO;
	  int i;
	  
	  // We get our current mutipart object
	  aMimeMultipart = (MimeMultipart *)[self content];

	  // We create our new multipart object for holding all our parts.
	  newMimeMultipart = [[MimeMultipart alloc] init];
	  
	  for (i = 0; i < [aMimeMultipart count]; i++)
	    {
	      aMimeBodyPart = [aMimeMultipart bodyPartAtIndex: i];
	      
	      if ( [aMimeBodyPart isMimeType: @"text": @"plain"] 
		   && !hasFoundTextPlain )
		{
		  MimeBodyPart *newMimeBodyPart;
		  
		  newMimeBodyPart = [[MimeBodyPart alloc] init];
		  
		  // We set the content of our new part
		  [aMutableString appendString: (NSString*)[aMimeBodyPart content]];
		  [newMimeBodyPart setContent: aMutableString];
		  [newMimeBodyPart setSize: [aMutableString length]];
		  
		  // We set the Content-Transfer-Encoding and the Charset to the previous one
		  [newMimeBodyPart setContentTransferEncoding: [aMimeBodyPart contentTransferEncoding]];
		  [newMimeBodyPart setCharset: [aMimeBodyPart charset]];
		  
		  // We finally add our new part to our MIME multipart object
		  [newMimeMultipart addBodyPart: newMimeBodyPart];
		  RELEASE(newMimeBodyPart);
		  
		  hasFoundTextPlain = YES;
		}
	      // We set the Content-Disposition to "attachment"
	      // all the time.
	      else
		{
		  [aMimeBodyPart setContentDisposition: @"attachment"];
		  [newMimeMultipart addBodyPart: aMimeBodyPart];
		}
	    }
	  
	  [theMessage setContent: newMimeMultipart];
	  RELEASE(newMimeMultipart);
	}
    }
  //
  // We got an unknown part. Let's inform the user about this situation.
  //
  else
    {
      // We set the content of our message
      [aMutableString appendString: @"The original message contained an unknown part that was not included in this forward message."];
      [aMutableString appendString: @"\n\nPlease report this as a bug."];
      
      [theMessage setContent: aMutableString];
      [theMessage setSize: [aMutableString length]];
    }
  
  return AUTORELEASE(theMessage);
}


//
//
//
- (NSData *) dataUsingSendingMode : (int) theMode
{
  NSMutableData *aMutableData;
  NSDictionary *aLocale;

  NSEnumerator *allHeaderKeyEnumerator;
  NSString *aKey;

  NSCalendarDate *aCalendarDate;

  NSData *aBoundary, *aData;

  char *lineTerminator;


  if ( theMode == SEND_USING_SMTP )
    {
      lineTerminator = CRLF;
    }
  else
    {
      lineTerminator = LF;
    }

  // We get our locale in English
#ifndef MACOSX
  aLocale = [NSDictionary dictionaryWithContentsOfFile: [NSBundle pathForGNUstepResource: @"English"
								  ofType: nil
								  inDirectory: @"Resources/Languages"] ];
#else
  aLocale = [NSDictionary dictionaryWithContentsOfFile: [[NSBundle bundleForClass:[NSObject class]]
							  pathForResource: @"English"
							  ofType: nil
							  inDirectory: @"Languages"] ];
#endif
  
  // We initialize our mutable data object holding the raw data of the
  // new message.
  aMutableData = [[NSMutableData alloc] init];
  
  aBoundary = [MimeUtility generateBoundary];
  
  // If we're sending this message to a local folder, we must append the From - tag.
  if ( theMode == SEND_TO_FOLDER )
    {
      [aMutableData appendCFormat: @"From -\n"];
    }

#ifndef MACOSX
  tzset();

  aCalendarDate = [[[NSDate alloc] init] dateWithCalendarFormat:@"%a, %d %b %Y %H:%M:%S %z"
					 timeZone: [NSTimeZone timeZoneWithAbbreviation: 
								 [NSString stringWithCString: tzname[1]]] ];
#else
  aCalendarDate = [[[NSDate alloc] init] dateWithCalendarFormat:@"%a, %d %b %Y %H:%M:%S %z"
					 timeZone: [NSTimeZone systemTimeZone] ];
#endif
  [aMutableData appendCFormat: @"Date: %@%s", [aCalendarDate descriptionWithLocale: aLocale],
		lineTerminator];
  
  // We set the subject, if we have one!
  if ( [[[self subject] stringByTrimmingWhiteSpaces] length] > 0 )
    {
      [aMutableData appendCString: "Subject: "];
      [aMutableData appendData: [MimeUtility encodeWordUsingQuotedPrintable: [self subject]
                                      prefixLength: 8]];
      [aMutableData appendCString: lineTerminator];
    }
  
  // We set our Message-ID
  [aMutableData appendCFormat:@"Message-ID: <"];
  [aMutableData appendData: [MimeUtility generateOSID]];
  [aMutableData appendCFormat: @">%s", lineTerminator];
  
  [aMutableData appendCFormat: @"MIME-Version: 1.0 (Generated by Pantomime %@)%s", PANTOMIME_VERSION, lineTerminator];

  // We encode our From: field
  [aMutableData appendCFormat: @"From: "];
  [aMutableData appendData: [[self from] dataValue]];
  [aMutableData appendCFormat: @"%s",lineTerminator];
  
  // We encode our To field
  aData = [self _formatRecipientsWithType: TO];
  
  if ( aData )
    {
      [aMutableData appendCString: "To: "];
      [aMutableData appendData: aData];
      [aMutableData appendCString: lineTerminator];
    }

  // We encode our Cc field
  aData = [self _formatRecipientsWithType: CC];
  
  if ( aData )
    {
      [aMutableData appendCString: "Cc: "];
      [aMutableData appendData: aData];
      [aMutableData appendCString: lineTerminator];
    }

  // We encode our Bcc field
  aData = [self _formatRecipientsWithType: BCC];
  
  if ( aData )
    {
      [aMutableData appendCString: "Bcc: "];
      [aMutableData appendData: aData];
      [aMutableData appendCString: lineTerminator];
    }
  
  // We set the Reply-To address in case we need to
  if ( [self replyTo] )
    {
      [aMutableData appendCFormat: @"Reply-To: "];
      [aMutableData appendData: [[self replyTo] dataValue]];
      [aMutableData appendCFormat: @"%s",lineTerminator];
    }
  
  // We set the Organization header value if we need to
  // FIXME: Should we quote this value?
  if ( [self organization] )
    {
      [aMutableData appendCFormat:@"Organization: %@%s", [self organization], lineTerminator];
    }
  
  // We set the In-Reply-To header if we need to
  if ( [self headerValueForName: @"In-Reply-To"] )
    {
      [aMutableData appendCFormat:@"In-Reply-To: %@%s", [self headerValueForName: @"In-Reply-To"], lineTerminator];
    }
  

  // We now set all X-* headers
  allHeaderKeyEnumerator = [[self allHeaders] keyEnumerator];
  
  while ( (aKey = [allHeaderKeyEnumerator nextObject]) ) 
    {
      if ( [aKey hasPrefix: @"X-"] )
	{
	  [aMutableData appendCFormat:@"%@: %@%s", aKey, [self headerValueForName: aKey], lineTerminator];
	}
    }
  
  //
  // We add our message header/body separator
  //
  [aMutableData appendData: [super dataUsingSendingMode: theMode]];

  return AUTORELEASE(aMutableData);
}


//
// This method is used to add an extra header to the list of headers
// of the message.
//
- (void) addHeader: (NSString *) theName
	 withValue: (NSString *) theValue
{
  [headers setObject: theValue forKey: theName];
}


- (id) headerValueForName: (NSString *) theName
{
  return [headers objectForKey: theName];
}

- (NSDictionary *) allHeaders
{
  return headers;
}

- (Folder *) folder
{
  return folder;
}

- (void) setFolder: (Folder *) theFolder
{
  folder = theFolder;
}


//
// This method initalize all the headers of a message
// from a raw data source.
//
// FIXME: Should we move most of the 'parsing' to the
// Parser class?
//
- (void) setHeadersFromData: (NSData *) theHeaders
{
  NSAutoreleasePool *pool;
  NSArray *allLines;
  int i;
  
  if (!theHeaders || [theHeaders length] == 0)
    {
      return;
    }

  // We initialize a local autorelease pool
  pool = [[NSAutoreleasePool alloc] init];

  // We MUST be sure to unfold all headers properly before
  // decoding the headers
  theHeaders = [MimeUtility unfoldLinesFromData: theHeaders];

  allLines = [theHeaders componentsSeparatedByCString: "\n"];

  for (i = 0; i < [allLines count]; i++)
    {
      NSData *aLine = [allLines objectAtIndex: i];

      // We stop if we found the header separator. (\n\n) since someone could
      // have called this method with the entire rawsource of a message.
      if ( [aLine length]==0 )
	{
	  break;
	}

      if ([aLine hasCaseInsensitiveCPrefix: "Bcc"])
	{
	  [Parser parseDestination:aLine
		  forType:BCC
		  inMessage:self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "Cc"])
	{
	  [Parser parseDestination:aLine
		  forType:CC
		  inMessage:self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "Content-Disposition"])
	{
	  [Parser parseContentDisposition:aLine inPart:self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "Content-Transfer-Encoding"])
	{
	  [Parser parseContentTransferEncoding:aLine inPart:self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "Content-Type"])
	{
	  [Parser parseContentType:aLine inPart:self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "Date"])
	{
	  [Parser parseDate:aLine inMessage:self];
	}
      else if ( [aLine hasCaseInsensitiveCPrefix: "From"] &&
		![aLine hasCaseInsensitiveCPrefix: "From -"] )
	{
	  [Parser parseFrom:aLine inMessage:self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "Message-ID"])
	{
	  [Parser parseMessageID:aLine inMessage:self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "MIME-Version"])
	{
	  [Parser parseMimeVersion:aLine inMessage:self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "Organization"])
	{
	  [Parser parseOrganization:aLine inMessage:self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "References"])
	{
	  [Parser parseReferences: aLine 
		  inMessage: self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "Reply-To"])
	{
	  [Parser parseReplyTo:aLine inMessage:self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "Resent-From"])
	{
	  [Parser parseResentFrom:aLine inMessage:self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "Resent-Bcc"])
	{
	  [Parser parseDestination:aLine
		  forType:RESENT_BCC
		  inMessage:self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "Resent-Cc"])
	{
	  [Parser parseDestination:aLine
		  forType:RESENT_CC
		  inMessage:self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "Resent-To"])
	{
	  [Parser parseDestination:aLine
		  forType:RESENT_TO
		  inMessage:self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "Status"])
	{
	  [Parser parseStatus:aLine inMessage:self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "To"])
	{
	  [Parser parseDestination:aLine
		  forType:TO
		  inMessage:self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "X-Status"])
	{
	  [Parser parseXStatus:aLine inMessage:self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "Subject"])
	{
	  [Parser parseSubject:aLine inMessage:self];
	}
      else
	{
	  [Parser parseUnknownHeader:aLine inMessage:self];
	}
    }

  RELEASE(pool);
}

//
//
//
- (void) setHeaders: (NSDictionary *) theHeaders
{
  RELEASE(headers);
  
  headers = [[NSMutableDictionary alloc] initWithCapacity: [theHeaders count]];
  [headers addEntriesFromDictionary: theHeaders];
}


//
// This method is used to optain tha raw source of a message.
// It's returned as a NSData object. The returned data should
// be the message like it's achived in a mbox format and it could
// easily be decoded with Message: -initWithData.
// 
// All subclasses of Message MUST implement this method.
//
- (NSData *) rawSource
{
  if ( !rawSource )
    {
      [self subclassResponsibility: _cmd];
      return nil;
    }
  
  return rawSource;
}


//
//
//
- (void) setRawSource: (NSData *) theRawSource
{
  if ( theRawSource )
    {
      RETAIN(theRawSource);
      RELEASE(rawSource);
      rawSource = theRawSource;
    }
  else
    {
      DESTROY(rawSource);
    }
}

//
//
//
- (NSCalendarDate *) resentDate
{
  return [headers objectForKey: @"Resent-Date"];
}

- (void) setResentDate: (NSCalendarDate *) theResentDate
{
  [headers setObject: theResentDate 
	   forKey: @"Resent-Date"];
}

- (InternetAddress *) resentFrom
{
  return [headers objectForKey: @"Resent-From"];
}

- (void) setResentFrom: (InternetAddress *) theInternetAddress
{
  [headers setObject: theInternetAddress
	   forKey: @"Resent-From"];
}

- (NSString *) resentMessageID
{
  return [headers objectForKey: @"Resent-Message-ID"];
}

- (void) setResentMessageID: (NSString *) theResentMessageID
{
  [headers setObject: theResentMessageID
	   forKey: @"Resent-Message-ID"];
}

- (NSString *) resentSubject
{
  return [headers objectForKey: @"Resent-Subject"];
}

- (void) setResentSubject: (NSString *) theResentSubject
{
  [headers setObject: theResentSubject
	   forKey: @"Resent-Subject"];
}

@end


//
// Message's sorting category
//
@implementation Message (Comparing)

- (int) compareAccordingToNumber: (Message *) aMessage
{
  int num1, num2;
  num1 = [self messageNumber];
  num2 = [aMessage messageNumber];
  if (num1 < num2)
    {
      return NSOrderedAscending;
    }
  else if (num1 > num2)
    {
      return NSOrderedDescending;
    }
  else
    {
      return NSOrderedSame;
    }
}

- (int) reverseCompareAccordingToNumber: (Message *) aMessage
{
  int num1, num2;
  num2 = [self messageNumber];
  num1 = [aMessage messageNumber];
  if (num1 < num2)
    {
      return NSOrderedAscending;
    }
  else if (num1 > num2)
    {
      return NSOrderedDescending;
    }
  else
    {
      return NSOrderedSame;
    }
}

- (int) compareAccordingToDate: (Message *) aMessage
{
  NSDate *date1 = [self receivedDate];
  NSDate *date2 = [aMessage receivedDate];
  NSTimeInterval timeInterval;

  if (date1 == nil || date2 == nil)
    {
      return [self compareAccordingToNumber: aMessage]; 
    }

  timeInterval = [date1 timeIntervalSinceDate: date2];

  if (timeInterval < 0)
    {
      return NSOrderedAscending;
    }
  else if (timeInterval > 0)
    {
      return NSOrderedDescending;
    }
  else
    {
      return [self compareAccordingToNumber: aMessage];      
    }
}

- (int) reverseCompareAccordingToDate: (Message *) aMessage
{
  NSDate *date2 = [self receivedDate];
  NSDate *date1 = [aMessage receivedDate];
  NSTimeInterval timeInterval;

  if (date1 == nil || date2 == nil)
    {
      return [self reverseCompareAccordingToNumber: aMessage]; 
    }

  timeInterval = [date1 timeIntervalSinceDate: date2];

  if (timeInterval < 0)
    {
      return NSOrderedAscending;
    }
  else if (timeInterval > 0)
    {
      return NSOrderedDescending;
    }
  else
    {
      return [self reverseCompareAccordingToNumber: aMessage];      
    }
}

- (int) compareAccordingToSender: (Message *) aMessage
{
  InternetAddress *from1, *from2;
  NSString *fromString1, *fromString2;
  NSString *tempString;
  int result;

  from1 = [self from];
  from2 = [aMessage from];

  tempString = [from1 personal];
  if (tempString == nil || [tempString length] == 0)
    {
      fromString1 = [from1 address];
      if (fromString1 == nil)
	fromString1 = @"";
    }
  else
    {
      fromString1 = tempString;
    }


  tempString = [from2 personal];
  if (tempString == nil || [tempString length] == 0)
    {
      fromString2 = [from2 address];
      if (fromString2 == nil)
	fromString2 = @"";
    }
  else
    {
      fromString2 = tempString;
    }

  result = [fromString1 caseInsensitiveCompare: fromString2];
  if (result == NSOrderedSame)
    {
	  return [self compareAccordingToNumber: aMessage];
    }
  else
    {
      return result;
    }
}

- (int) reverseCompareAccordingToSender: (Message *) aMessage
{
  InternetAddress *from1, *from2;
  NSString *fromString1, *fromString2;
  NSString *tempString;
  int result;

  from2 = [self from];
  from1 = [aMessage from];

  tempString = [from1 personal];
  if (tempString == nil || [tempString length] == 0)
    {
      fromString1 = [from1 address];
      if (fromString1 == nil)
	fromString1 = @"";
    }
  else
    {
      fromString1 = tempString;
    }


  tempString = [from2 personal];
  if (tempString == nil || [tempString length] == 0)
    {
      fromString2 = [from2 address];
      if (fromString2 == nil)
	fromString2 = @"";
    }
  else
    {
      fromString2 = tempString;
    }


  result = [fromString1 caseInsensitiveCompare: fromString2];
  
  if (result == NSOrderedSame)
    {
      return [self reverseCompareAccordingToNumber: aMessage];
    }
  else
    {
      return result;
    }
}

- (int) compareAccordingToSubject: (Message *) aMessage
{
  NSString *subject1 = [self subject];
  NSString *subject2 = [aMessage subject];
  int result;
  
  if (subject1 == nil)
    subject1 = @"";
  if (subject2 == nil)
    subject2 = @"";

  result = [subject1 caseInsensitiveCompare: subject2];

  if (result == NSOrderedSame)
    {
      return [self compareAccordingToNumber: aMessage];      
    }
  else
    {
      return result;
    }
}

- (int) reverseCompareAccordingToSubject: (Message *) aMessage
{
  NSString *subject2 = [self subject];
  NSString *subject1 = [aMessage subject];
  int result;
  
  if (subject1 == nil)
    subject1 = @"";
  if (subject2 == nil)
    subject2 = @"";

  result = [subject1 caseInsensitiveCompare: subject2];

  if (result == NSOrderedSame)
    {
      return [self compareAccordingToNumber: aMessage];      
    }
  else
    {
      return result;
    }
}

- (int) compareAccordingToSize: (Message *) aMessage
{
  int size1 = [self size];
  int size2 = [aMessage size];


  if (size1 < size2)
    {
      return NSOrderedAscending;
    }
  else if (size1 > size2)
    {
      return NSOrderedDescending;
    }
  else
    {
      return [self compareAccordingToNumber: aMessage];
    }
}

- (int) reverseCompareAccordingToSize: (Message *) aMessage
{
  int size1 = [aMessage size];
  int size2 = [self size];


  if (size1 < size2)
    {
      return NSOrderedAscending;
    }
  else if (size1 > size2)
    {
      return NSOrderedDescending;
    }
  else
    {
      return [self reverseCompareAccordingToNumber: aMessage];      
    }
}

@end


//
// Private methods
//
@implementation Message (Private)

- (NSData *) _formatRecipientsWithType: (int) theType
{
  NSMutableData *aMutableData;
  NSArray *anArray;
  int i;

  aMutableData = [[NSMutableData alloc] init];
  anArray = [self recipients];

  for (i = 0; i < [anArray count]; i++)
    {
      InternetAddress *anInternetAddress;

      anInternetAddress = [anArray objectAtIndex: i];

      if ([anInternetAddress type] == theType)
	{
	  [aMutableData appendData: [anInternetAddress dataValue]];
	  [aMutableData appendCString: ", "];
	}
    }
  
  if ( [aMutableData length] > 0)
    {
      [aMutableData setLength: [aMutableData length] - 2];

      return AUTORELEASE(aMutableData);
    }
  else
    {
      RELEASE(aMutableData);
      
      return nil;
    }
}

@end


